﻿using System;
using O1.Kernel.Primitives;
using O1.Kernel.Storage;
using O1.Kernel.Storage.Transactions;

namespace O1.Kernel.IO
{
    internal class ShardedVertexReader : InputAdapter
    {
        private readonly MemoryTrunk store;

        private readonly ConcurrentScope token;

        private readonly byte[] master;

        private byte[] vertex;

        private int vertextOffset;

        private int masterOffset;

        public ShardedVertexReader(MemoryTrunk trunk, ConcurrentScope token, long vertexId)
            : this(trunk, token, trunk.ReadMasterRecord(vertexId), trunk.ReadVertex(vertexId, token))
        {
        }

        public ShardedVertexReader(MemoryTrunk store, ConcurrentScope token, byte[] master, byte[] vertex)
        {
            this.store = store;
            this.token = token;
            this.master = master;
            this.vertex = vertex;
            this.masterOffset = MasterCell.Size;
        }

        protected override int ReadToBuffer(byte[] buffer, int offset, int count)
        {
            var vbuffer = this.vertex;
            if (vbuffer == null)
            {
                return 0;
            }

            var voffset = this.vertextOffset;
            if (voffset + count < vbuffer.Length)
            {
                Array.Copy(vbuffer, voffset, buffer, offset, count);
                this.vertextOffset = voffset + count;
                return count;
            }

            var difference = vbuffer.Length - voffset;
            Array.Copy(vbuffer, voffset, buffer, offset, difference);

            // Try to read the next shard:
            var mbuffer = this.master;
            if (mbuffer == null)
            {
                this.vertextOffset = voffset + difference;
                this.vertex = null;
                return difference;
            }

            CellAddress nextShard;
            var moffset = MasterCell.NextShard(buffer, this.masterOffset, out nextShard);
            if (moffset == -1)
            {
                this.vertextOffset = voffset + difference;
                this.vertex = null;
                return difference;
            }

            this.masterOffset = moffset;
            vbuffer = this.store.ReadVertex(nextShard.CellId, this.token);
            if (vbuffer == null)
            {
                this.vertextOffset = voffset + difference;
                this.vertex = null;
                return difference;
            }

            this.vertex = vbuffer;
            this.vertextOffset = 0;
            var remainder = count - difference;
            difference += this.ReadToBuffer(buffer, difference, remainder);
            return difference;
        }
    }
}